package com.gitee.wsl.struct.generator.sample

import com.gitee.wsl.ext.base.currentTimeMillis
import com.gitee.wsl.ext.string.toBinaryString
import com.gitee.wsl.struct.generator.sample.CardIdGeneratorable.Companion.getValidationCode
import com.gitee.wsl.struct.generator.sample.ShopCardIdGenerator.Companion.getShopCode
import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized


/**
 * 激活码生成器。激活码有如下特点：
 * 1. 激活码固定12位，全大写字母。
 * 2. 激活码生成时植入关联的卡号的Hash，但是不可逆；即无法从激活码解析出卡号，也无法从卡号解析出激活码。
 * 3. 激活码本质上是一个正整数，通过一定的编码规则转换成全大写字符。
 * ** 以字符A来说，可能在“KMLVAPPGRABH”激活码中代表数字4，在"MONXCRRIUNVA"激活码中代表数字23。即每个大写字符都可以代表0-25的任一数字。
 * 4. 具体使用何种编码规则，是通过时间戳+店铺编号Hash决定的。
 * 5. 校验激活码分为两个步骤。(1). 首先校验激活码的合法性 (2). 步骤1校验通过后，从数据库查询出关联的卡号，对卡号和激活码的关系做二次校验
 * 激活码的正整数由51bit组成
 * +==============================================================================================
 * | 4bit店铺编号校验位 | 29bit时间戳 | 3bit机器编号  | 7bit序号 | 4bit激活码校验位 | 4bit卡号校验位 |
 * +==============================================================================================
 * 29 bit的秒时间戳支持17年，激活码生成器计时从2017年开始，可以使用到2034年
 * 7 bit序号支持128个序号
 * 3 bit机器编号支持8台负载
 * 即激活码生成最大支持8台负载，每台负载每秒钟可以生成128个激活码，整个系统1秒钟可以生成1024个激活码
 * 时间戳、机器编号、序号和校验位的bit位数支持业务自定义，方便业务定制自己的生成器。
 *
 * @author liuzhaoming
 * @date 2018/1/20
 */

class ActivationCodeGenerator: SynchronizedObject {
    /**
     * 字符字典
     */
    private lateinit var alphabet: CharArray

    /**
     * 时间bit数，时间的单位为秒，29 bit位时间可以表示17年
     */
    private var timeBits = 29

    /**
     * 机器编码bit数
     */
    private var machineBits = 3

    /**
     * 每秒序列bit数
     */
    private var sequenceBits = 7

    /**
     * 校验bit位数
     */
    private var validationBits = 4

    /**
     * 卡号校验bit位数
     */
    private val cardIdBits = 4

    /**
     * 上一次时间戳
     */
    private var lastStamp = -1L

    /**
     * 系统编号左移bit数
     */
    private var shopOffset = 0

    /**
     * 序列
     */
    private var sequence: Long = currentTimeMillis % 10

    /**
     * 机器编号
     */
    private var machineId = 1L

    /**
     * 时间左移bit数
     */
    private var timeOffset = 0

    /**
     * 机器编码左移bit数
     */
    private var machineOffset = 0

    /**
     * 序列左移bit数
     */
    private var sequenceOffset = 0

    /**
     * 校验码左移bit数
     */
    private var validationOffset = 0

    /**
     * 最大序列号
     */
    private var maxSequence = 0L

    /**
     * 最大校验码
     */
    private var maxCode: Long = 0

    /**
     * 最大店铺编号校验码
     */
    private var maxShopCode = 0L

    /**
     * 最大卡号校验码
     */
    private var maxCardIdCode = 0L

    /**
     * 开始时间，默认为2019-01-01
     */
    private var startTimeString = "2019-01-01 00:00:00"

    /**
     * 起始时间戳
     */
    private var startTimeStamp = 0L

    /**
     * 构造函数
     *
     * @param alphabetsString 字符集编码字符串, 应该是26个大写字符的乱序
     */
    constructor(alphabetsString: String?) : this(1, alphabetsString)

    /**
     * 构造函数
     *
     * @param machineId       机器ID, 如果有多个负载，那么机器ID需要不同
     * @param alphabetsString 字符集编码字符串, 应该是26个大写字符的乱序
     */
    constructor(machineId: Int, alphabetsString: String?) {
        val maxMachineId = (-1 shl machineBits).inv()
        if (machineId > maxMachineId) {
            throw IllegalArgumentException(
                "Machine bits is " + machineBits
                        + ", so the max machine id is " + maxMachineId
            )
        }

        this.machineId = machineId.toLong()
        init()
        parseAlphabets(alphabetsString)
    }

    /**
     * 全参构造函数，便于业务定制卡号生成器
     *
     * @param timeBits        时间bit数
     * @param machineBits     机器编码bit数
     * @param sequenceBits    每秒序列bit数
     * @param validationBits  校验bit位数
     * @param cardIdBits      卡号校验位
     * @param machineId       机器编号
     * @param startTimeString 开始时间，格式为2019-01-01 00:00:00
     * @param alphabetsString 字符集编码字符串,应该是26个大写字符的乱序
     */
    constructor(
        timeBits: Int, machineBits: Int, sequenceBits: Int, validationBits: Int, cardIdBits: Int,
        machineId: Int, startTimeString: String?, alphabetsString: String?,
    ) {
        if (timeBits <= 0 || machineBits <= 0 || sequenceBits <= 0 || validationBits <= 0 || cardIdBits <= 0) {
            throw IllegalArgumentException("The bits should be larger than 0")
        }
        if (timeBits + machineBits + sequenceBits + validationBits + cardIdBits != 47) {
            throw IllegalArgumentException(
                "The sum of timeBits and machineBits and sequenceBits " +
                        "and validationBits and cardIdBits should be 47"
            )
        }

        val maxMachineId = (-1 shl machineBits).inv()
        if (machineId > maxMachineId) {
            throw IllegalArgumentException("Machine bits is $machineBits, so the max machine id is $maxMachineId")
        }

        this.timeBits = timeBits
        this.machineBits = machineBits
        this.sequenceBits = sequenceBits
        this.validationBits = validationBits
        this.machineId = machineId.toLong()
        if (null != startTimeString) {
            this.startTimeString = startTimeString
        }
        init()
        parseAlphabets(alphabetsString)
    }

    /**
     * 根据给定的系统编号生成激活码
     *
     * @param shopId 店铺编号
     * @return 12位大写字符串激活码
     */

    fun generate(shopId: String, cardId: Long): String = synchronized(this){
        if (shopId.isEmpty()) {
            throw IllegalArgumentException("Shop id and card id cannot be null")
        }

        var curStamp = newSecond
        if (curStamp < lastStamp) {
            throw IllegalArgumentException("Clock moved backwards. Refusing to generate id")
        }

        if (curStamp == lastStamp) {
            sequence = (sequence + 1) and maxSequence
            if (sequence == 0L) {
                curStamp = nextSecond
            }
        } else {
            sequence = currentTimeMillis % 10
        }

        lastStamp = curStamp
        val shopCode: Long = getShopCode(shopId, maxShopCode)
        val originId = shopCode shl shopOffset or ((curStamp - startTimeStamp) shl timeOffset
                ) or (machineId shl machineOffset
                ) or (sequence shl sequenceOffset)

        val validationCode: Long = getValidationCode(originId, maxCode)
        val cardIdCode: Long = getValidationCode(cardId, maxCardIdCode)
        val totalCode = (validationCode shl validationOffset) + cardIdCode
        val originCode = originId + totalCode

        //编码序号
        val encodeIndex: Int = ((shopCode + currentTimeMillis) % 26).toInt()
        val originCodeString = originCode.toString(26)
        val sb: StringBuilder = StringBuilder()
        sb.append(alphabet[encodeIndex])
        for (character in originCodeString.toCharArray()) {
            val encodeChar = encodeChar(character, encodeIndex)
            sb.append(encodeChar)
        }
        return sb.toString()
    }


    /**
     * 校验激活码是否合法
     *
     * @param shopId 店铺编号
     * @param code   激活码
     * @return boolean 合法返回true，反之false
     */
    fun validate(shopId: String, code: String): Boolean {
        try {
            if (!validateCode(code)) {
                return false
            }

            val longCode = getLongId(code)
            val bitString: String = longCode.toString().toBinaryString()
            val bitLength = bitString.length

            val shopCode: Long = getShopCode(shopId, maxShopCode)
            val shopCodeBitString = bitString.substring(0, bitLength - shopOffset)
            val parseShopCode = shopCodeBitString.toLong(2)
            return shopCode == parseShopCode
        } catch (e: Exception) {
            return false
        }
    }

    /**
     * 校验激活码和卡号是否匹配
     *
     * @param code   激活码
     * @param cardId 卡号
     * @return 匹配返回true，反之false
     */
    fun validateCardId(code: String, cardId: Long): Boolean {
        try {
            val longCode = getLongId(code)
            val bitString: String = longCode.toString().toBinaryString()
            val cardIdBitString = bitString.substring(bitString.length - cardIdBits)
            val parseCardIdCode = cardIdBitString.toLong(2)
            val cardIdCode: Long = getValidationCode(cardId, maxCardIdCode)

            return parseCardIdCode == cardIdCode
        } catch (e: Exception) {
            return false
        }
    }

    /**
     * 解析激活码
     *
     * @param code 激活码
     * @return 解析结果依次是时间戳、机器编码、序列号
     */
    fun parse(code: String): Array<Long>? {
        if (!validateCode(code)) {
            return null
        }

        val bitString: String = getLongId(code).toString().toBinaryString()
        val bitLength = bitString.length
        val timestamp =
            bitString.substring(bitLength - timeOffset - timeBits, bitString.length - timeOffset)
                .toLong(2)
        val machineId = bitString.substring(
            bitLength - machineOffset - machineBits,
            bitString.length - machineOffset
        ).toLong(2)
        val sequence = bitString.substring(
            bitLength - sequenceOffset - sequenceBits,
            bitString.length - sequenceOffset
        ).toLong(2)
        return arrayOf((timestamp + startTimeStamp) * 1000, machineId, sequence)
    }

    /**
     * 数据初始化
     */
    private fun init() {
        validationOffset = cardIdBits
        sequenceOffset = validationOffset + validationBits
        machineOffset = sequenceOffset + sequenceBits
        timeOffset = machineOffset + machineBits
        shopOffset = timeOffset + timeBits
        maxSequence = (-1L shl sequenceBits).inv()
        maxCode = (-1L shl validationBits).inv()
        maxCardIdCode = (-1 shl cardIdBits).toLong().inv()
        startTimeStamp = CardIdGeneratorable.getTimeStampSecond(startTimeString)
        maxShopCode = 14L
    }


    /**
     * 解析字符集编码
     *
     * @param alphabetsString 字符集编码字符串，应该是26个大写字符的乱序
     */
    private fun parseAlphabets(alphabetsString: String?) {
        if (null == alphabetsString || alphabetsString.length != 26) {
            throw IllegalArgumentException("Invalid alphabet string")
        }

        alphabet = alphabetsString.toCharArray()
    }

    /**
     * 校验除店铺编号外的所有字段
     *
     * @param id id
     * @return boolean 合法返回true
     */
    private fun validateCode(id: String?): Boolean {
        if (null == id || id.length != 12) {
            return false
        }

        for (character in id.toCharArray()) {
            if (character < 'A' || character > 'Z') {
                return false
            }
        }

        val longId = getLongId(id)
        val bitString: String = longId.toString().toBinaryString()
        val bitLength = bitString.length

        val codeBitString = bitString.substring(bitLength - validationBits - cardIdBits)
        val validationCodeBitString =
            bitString.substring(bitLength - validationBits - cardIdBits, bitLength - cardIdBits)
        val totalValidationCode = codeBitString.toInt(2)
        val validationCode = validationCodeBitString.toInt(2)
        val originId = longId - totalValidationCode
        val parseValidationCode: Long = getValidationCode(originId, maxCode)
        if (validationCode.toLong() != parseValidationCode) {
            return false
        }

        val timestamp =
            bitString.substring(bitLength - timeBits - timeOffset, bitLength - timeOffset).toLong(2)
        val currentStamp: Long = currentTimeMillis / 1000 - startTimeStamp
        val timeDelta = currentStamp - timestamp
        return timeDelta > -3600
    }

    /**
     * 将大写字母组成的激活码转换为Long
     *
     * @param id 激活码
     * @return Long
     */
    private fun getLongId(id: String): Long {
        val encodeChar = id[0]
        val encodeIndex = getCharIndex(encodeChar)
        val sb: StringBuilder = StringBuilder()
        for (character in id.substring(1).toCharArray()) {
            val decodedChar = decodeChar(character, encodeIndex)
            sb.append(decodedChar)
        }

        return sb.toString().toLong(26)
    }

    private val newSecond: Long
        /**
         * 获取当前时间戳 单位秒
         *
         * @return 时间戳（秒）
         */
        get() = currentTimeMillis / 1000

    private val nextSecond: Long
        /**
         * 获取下一秒钟
         *
         * @return 时间戳（秒）
         */
        get() {
            var second = newSecond
            while (second <= lastStamp) {
                //IdUtils.sleep(20)
                second = newSecond
            }
            return second
        }

    /**
     * 字符编码
     *
     * @param character   原始字符
     * @param encodeIndex 编码索引
     * @return 编码后的字符
     */
    private fun encodeChar(character: Char, encodeIndex: Int): Char {
        val actualValue =
            if (character >= 'a') character.code - 'a'.code + 10 else character.code - '0'.code
        val index = (actualValue + encodeIndex) % 26
        return alphabet[index]
    }

    /**
     * 获取编码索引
     *
     * @param character 字符
     * @return 编码索引
     */
    private fun getCharIndex(character: Char): Int {
        for (i in alphabet.indices) {
            if (alphabet[i] == character) {
                return i
            }
        }

        return -1
    }

    /**
     * 解码字符
     *
     * @param character   编码后的字符
     * @param encodeIndex 编码索引
     * @return 解码后的字符
     */
    private fun decodeChar(character: Char, encodeIndex: Int): Char {
        val index = getCharIndex(character)
        val actualValue = (index - encodeIndex + 26) % 26
        return if (actualValue < 10) (actualValue + '0'.code).toChar() else (actualValue - 10 + 'a'.code).toChar()
    }

    companion object {
        /**
         * 生成随机的字符编码，供应用初始化时使用
         *
         * @return 字符编码
         */
        fun generateAlphabets(): String {
            val template = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
            return generateAlphabets(template, 1)
        }

        /**
         * 生成指定数目随机的字符编码，用于数据结构初始化
         *
         * @param alphabet 允许的字符集
         * @param size     字符编码集数目
         * @return 字符编码集
         */
        fun generateAlphabets(alphabet: String, size: Int): String {
            /*List<Character> templateCharList = alphabet.chars()
                .mapToObj(c -> (char) c)
                .collect(Collectors.toList());*/

            val templateCharList: List<Char> = alphabet.toList()

            val alphabets = arrayOfNulls<String>(size)
            for (i in 0 until size) {
                templateCharList.shuffled()
                val sb = StringBuilder()
                for (currentChar in templateCharList) {
                    sb.append(currentChar)
                }
                alphabets[i] = sb.toString()
            }

            return alphabets.joinToString(",")
        }
    }
}
